/*
 * Copyright (C) 2012 Prasanta Paul, http://prasanta-paul.blogspot.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package krow.dev.addetector.control.decompile;


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import krow.dev.addetector.control.decompile.xml.XmlConverter;
import krow.dev.addetector.control.decompile.xml.XmlGenerator;

/**
 * Unpackage APK file with readable class, resources and XML.
 * You can Read AndroidManifest.xml from code using- android.content.pm.PackageManager.queryXX
 * TODO: 
 * 1. Parse resources.arsc
 * 2. Fail free. It shouldn't halt once first error occures.
 * 
 * @author Prasanta Paul
 *
 */
public class Extractor {

	final int BUFFER = 2048;
	ArrayList<String> xmlFiles = new ArrayList<String>();
	String dexFile = null;
	String resFile = null;
	boolean debug = true;
	
	String tag = getClass().getSimpleName();
	
	/**
	 * APK files are Zip file. Using Java Zip util to decompress APK resources.
	 * 1. Decompress
	 * 2. Decode Android Binary XML (Manifest and Layout files)
	 * 3. Convert Dex/ODex files to Jar.
	 * 
	 * @param apkFile
	 * @throws Exception
	 */
	public void unZip(String apkFile) throws Exception 
	{
		File file = new File(apkFile);
	
		/*
		 * Create the Base Directory, whose name should be same as Zip file name.
		 * All decompressed contents will be placed under this folder.
		 */
		String apkFileName = file.getName();
		
		if(apkFileName.indexOf('.') != -1)
			apkFileName = apkFileName.substring(0,  apkFileName.indexOf('.'));
		
		File extractFolder = new File((file.getParent() == null ? "" : file.getParent() + File.separator) + apkFileName);
		if(!extractFolder.exists())
			extractFolder.mkdir();
		
		/*
		 * Read zip entries.
		 */
		FileInputStream fileInputStream = new FileInputStream(apkFile);
		ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(fileInputStream));
		
		/*
		 * Zip InputStream shifts its index to every Zip entry when getNextEntry() is called.
		 * If this method returns null, Zip InputStream reaches EOF.
		 */
		ZipEntry zipEntry = null;
		BufferedOutputStream bufferedOutputStream;
		
		while((zipEntry = zipInputStream.getNextEntry()) != null)
		{
			/*
			 * Create decompressed file for each Zip entry. A Zip entry can be a file or directory.
			 * ASSUMPTION: APK Zip entry uses Unix style File seperator- "/"
			 * 
			 * 1. Create the prefix Zip Entry folder, if it is not yet available
			 * 2. Create the individual Zip Entry file.
			 */
			String entryName = zipEntry.getName();
			String folderName = entryName;
			
			if(zipEntry.isDirectory())
			{
				entryName = null; // Don't create  Zip Entry file
			}
			else{
				if(entryName.indexOf("/") == -1) // Zip entry uses "/"
					folderName = null; // It is File. don't create Zip entry Folder
				else {
					folderName = entryName.substring(0, entryName.lastIndexOf("/"));
					entryName = entryName.substring( entryName.lastIndexOf("/") + 1);
				}
			}
			
			
			// Create Zip Entry Folder
			File entryFile = extractFolder;
			if(folderName != null)
			{
				entryFile = new File(extractFolder.getPath() + File.separator + folderName);
				if(!entryFile.exists())
					entryFile.mkdirs();
			}
			
			// Create Zip Entry File
			if(entryName == null)
				continue;
			
			// Keep track of XML files, they are in Android Binary XML format
			if(entryName.endsWith(".xml"))
				xmlFiles.add(entryFile.getPath() + File.separator + entryName);
			
			// Keep track of the Dex/ODex file. Need to convert to Jar
			if(entryName.endsWith(".dex") || entryName.endsWith(".odex"))
				dexFile = entryFile.getPath() + File.separator + entryName;
			
			// Keep track of Resources.arsc file- resources.arsc
			if(entryName.endsWith(".arsc"))
				resFile = entryFile.getPath() + File.separator + entryName;
			
			// Write Zip entry File to the disk
			int count;
			byte data[] = new byte[BUFFER];
			
			FileOutputStream fileOutputStream = new FileOutputStream(entryFile.getPath() + File.separator + entryName);
			bufferedOutputStream = new BufferedOutputStream(fileOutputStream, BUFFER);

			while ((count = zipInputStream.read(data, 0, BUFFER)) != -1) 
			{
				bufferedOutputStream.write(data, 0, count);
			}
			bufferedOutputStream.flush();
			bufferedOutputStream.close();
		}

		// Close Zip InputStream
		zipInputStream.close();
		fileInputStream.close();
	}
	
	public String getManifest(String apkFile) throws Exception
	{
		/*
		 * Read zip entries.
		 */
		FileInputStream fileInputStream = new FileInputStream(apkFile);
		ZipInputStream zipInputSream = new ZipInputStream(new BufferedInputStream(fileInputStream));
		
		/*
		 * Zip InputStream shifts its index to every Zip entry when getNextEntry() is called.
		 * If this method returns null, Zip InputStream reaches EOF.
		 */
		ZipEntry zipEntry = null;
//		BufferedOutputStream dest;
		
		byte[] binaryManifest = null;
		while((zipEntry = zipInputSream.getNextEntry()) != null)
		{
			if (zipEntry.getName().equalsIgnoreCase("AndroidManifest.xml"))
			{
//				String zeName = ze.getName();
//				String zeFolder = zeName;

				if(zipEntry.isDirectory())
					continue;
				
				// Write Zip entry File to string
				int count;
				byte data[] = new byte[BUFFER];
				
				
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
//				FileOutputStream fos = new FileOutputStream(zeFile.getPath() + File.separator + zeName);
//				dest = new BufferedOutputStream(fos, BUFFER);

				while ((count = zipInputSream.read(data, 0, BUFFER)) != -1) 
				{
					baos.write(data, 0, count);
					//dest.write(data, 0, count);
				}
				baos.flush();
				baos.close();
				
				binaryManifest = baos.toByteArray();
				
				break;
			}
		
		}

		// Close Zip InputStream
		zipInputSream.close();
		fileInputStream.close();
		
		
		XmlConverter xmlConverter;
		XmlGenerator xmlGenerator = new XmlGenerator();

		xmlConverter = new XmlConverter(xmlGenerator);
		xmlConverter.parse(binaryManifest);
		
		xmlConverter = null;

		
		return xmlGenerator.getXml();
	}
	
	/**
	 * Decode binary XML files
	 */
	public void decodeBX() throws Exception {
		// Convert WBXML to XML
		/*
		 * aapt (Android Assents Packaging Tool) converts XML files to Android Binary XML. It is not same as
		 * WBXML format.
		 */
		for(int i = 0; i<xmlFiles.size(); i++)
		{
			
			// 23rd March, 2012. Prasanta. 
			// Skip exception while parsing any file and complete the complete parsing cycle.
			XmlConverter xmlConverter;
			try{
				xmlConverter = new XmlConverter(new XmlGenerator());
				xmlConverter.parse(xmlFiles.get(i));
				
			}catch(Exception e){
			}
			finally{
				xmlConverter = null;
			}
		}
	}
	
	/**
	 * Convert .dex/.odex file to .jar.
	 *  .jar file will contain .class files which can be decompiled.
	 *  Works with ICS APK.
	 *  
	 * Dex2Jar Lib- 
	 * http://code.google.com/p/dex2jar
	 * 
	 * Decompiler- JD GUI
	 * http://java.decompiler.free.fr/?q=jdgui
	 */
	
	/* Mhill - I don't need this for now...
	public void decodeDex() throws Exception {
		Log.p(tag, "Decode DEX/ODEX...");
		if(dexFile == null){
			Log.p("No .dex/.odex file. Skip decodeDex()");
			return;
		}
		String jarFile = dexFile + ".jar";
		// Read Dex file
		Dex2jar dj = Dex2jar.from(new File(dexFile));
		
		// Create Jar
		dj.to(jarFile);
		Log.p("Converted Dex/ODex to Jar.");
		Log.p("I'm Done! ....huh :-)");
	}
	*/
	
	/**
	 * TODO: more work to do. :-(
	 * 
	 * @throws Exception
	 */
	public void decodeResource() throws Exception {
		XmlConverter xmlConverter = new XmlConverter(null);
		xmlConverter.parseResourceTable(resFile);
	}
}
